浅析组件化时代的前端状态管理(六):React Hook

React Hooks目前是 React 处于提案阶段的一个特性, 根据其官方的介绍, Hooks被创建出来主要动机如下:

  • 跨组件复用stateful logic(包含状态的逻辑)十分困难

  • 复杂的组件难以理解

  • Hooks让React组件也是更接近于函数

很多时候组件之间的逻辑都是相似的, 如果想复用组件中的逻辑 或者扩展组件的逻辑, 我们可以通过 render props 或者高阶组件, 所以我们在使用很多开源的组件时, 可以经常看到组件支持传入一个 函数作为 props. 通过这个方式, 我们甚至可以完全重新定义组件的具体渲染

使用 render props 和 stateless component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React, { PureComponent } from "react";

//举个例子, 现在要开发一个列表组件应用, 点击某一行, 就会在这行后面添加字符串 "已选择"

const ListItemComponent = (props = {}) => {
const { isSelected, onClick, value } = props;
console.log(value);
return (
<li onClick={onClick}>
<span>{value}</span>
{isSelected ? "已选择" : ""}
</li>
);
};

class App extends PureComponent {
constructor(props) {
super(props);
this.data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
this.state = {
selectedList: []
};
this.onClick = this.onClick.bind(this);
}
onClick(e, value) {
const newSelectedList = this.state.selectedList.concat([value]);
this.setState({
selectedList: newSelectedList
});
}

render() {
return (
<>
demo 6_1:
<br />
{this.data.map(item => {
// ListItemComponent 可以作为props的一个属性直接传递进来, 相当于 ListItemComponent的逻辑部分都是可以直接复用的.
return (
<ListItemComponent
value={item}
onClick={event => this.onClick(event, item)}
isSelected={this.state.selectedList.includes(item)}
/>
);
})}
</>
);
}
}

export default App;

Edit front_end_state_manage_demo

在真实的项目中, 一个组件的场景会比上述复杂的多, 可能需要拆分成非常多的子组件, 改造成本也比较高. 当项目中比较复杂的组件代码都改造为多个小的 stateless 组件, 并通过 render props 和 高阶组件的方式进行组装, 组件的可扩展性和代码的可复用性将会极大的提高.

本来只是想复用组件中的代码逻辑, 最后却重构整个组件, React 团队认为这种现象也指出了一些更深层次的问题:React需要一些更好的底层元素来复用stateful logic.

使用 React Hooks实现上述的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { useState } from "react";

const ListItemComponent = (props = {}) => {
const [isSelected, setIsSelected] = useState(false);
const { value } = props;
return (
<li onClick={() => setIsSelected(!isSelected)}>
<span>{value}</span>
{isSelected ? "已选择" : ""}
</li>
);
};

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const App = () => {
return (
<>
demo 6_2:
<br />
{data.map(item => (
<ListItemComponent value={item} />
))}
</>
);
};

export default App;

Edit front_end_state_manage_demo

从上面使用 React Hooks改造过的代码中, 可以直观的看到, 在ListItemComponent里面可以直接创建一个 值 和 修改值的方法, 就不用像 render props那样子需要把 “值 和 修改值的方法” 从父组件 一层层的传递到子组件. 利用useState这个 hooks, 等同于把依赖的状态数据注入组件, 只是这些数据并非是集中管理的, 而是分散且粒度更细的数据, 关于数据共享 和 数据修改, 还有useReducer 和 useContext 等hooks可以提供能力支持

React hooks 只是逻辑的复用, 并非数据的复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useState } from "react";

function useIsSelected(friendID) {
const [isSelected, setIsSelected] = useState(false);

function handleStatusChange(e) {
setIsSelected(!isSelected);
}
return [isSelected, handleStatusChange];
}

function List1() {
const [selected, actions] = useIsSelected();

return <span onClick={actions}>list1(点我) {selected ? "选中" : ""} </span>;
}

function List2() {
const [selected, actions] = useIsSelected();
return <span onClick={actions}>list2(点我) {selected ? "选中" : ""}</span>;
}

function App() {
return (
<>
demo 6_3:
<br />
<List1 />
<br />
<List2 />
</>
);
}

export default App;

Edit front_end_state_manage_demo

上述的 List1 List2 都复用 useIsSelected, 但是只是逻辑的复用, 实际上会创建两个独立的值, 互不干扰.

React hooks 本质上只是数组

React Hooks 带来两个使用上的约定:

  • 不要在循环,条件判断,嵌套函数里面调用 Hooks
  • 只在 React 的函数里面调用 Hooks

其实很好理解, 每次使用 useXXX Hooks的时候, 是通过两个数组来存储对应的 值 和修改值的方法, 原理详细参考

React Hooks 这部分就草草的收尾了, 一方面是这块确实没有太多的实践经验, 纯属是在理论上做简单的分析与介绍, 更多的 Hooks 细节介绍由于有 官方文档, 就不重复阐述,
React Hooks 部分也可以参考: 精读《React Hooks》